Skip to content

chore: use gt.config.json and _gt/<locale>.json for example translations#20

Open
moss-bryophyta wants to merge 3 commits intogeneraltranslation:mainfrom
moss-bryophyta:moss/update-examples
Open

chore: use gt.config.json and _gt/<locale>.json for example translations#20
moss-bryophyta wants to merge 3 commits intogeneraltranslation:mainfrom
moss-bryophyta:moss/update-examples

Conversation

@moss-bryophyta
Copy link
Contributor

@moss-bryophyta moss-bryophyta commented Mar 8, 2026

Refactors all four examples (fastapi-eager, fastapi-lazy, flask-eager, flask-lazy) to:

  • Read config from gt.config.jsondefaultLocale and locales are now defined in a config file rather than hardcoded in the app
  • Store translations in _gt/<locale>.json — each locale gets its own JSON file with hash-keyed translations
  • Use a load_translations function that reads from the _gt/ directory

The custom load_translations function is still required for now but will be removed in a future release.

Tests are unchanged — they create their own self-contained app fixtures.

Greptile Summary

This PR refactors all four example applications (fastapi-eager, fastapi-lazy, flask-eager, flask-lazy) to externalize configuration and translations: locale settings move from hardcoded values to gt.config.json, and translation strings move from inline dictionaries to per-locale _gt/<locale>.json files. The changes align the examples with the intended file-based workflow that users of the library would follow in practice.

Key changes across all four apps:

  • Config externalisation: defaultLocale and locales are now read from gt.config.json via a module-level json.load() call.
  • Translation file loading: The inline TRANSLATIONS dict is replaced by a load_translations(locale) function that reads _gt/<locale>.json from disk, returning {} for unknown locales.
  • Debug print() statements removed: The [eager]/[lazy] loading prints that were in the old load_translations functions are gone — a clean improvement.
  • "en" omitted from locales in config: The gt.config.json files only list ["es", "fr"]. This is correct because I18nManager always adds default_locale to its internal locale set, so "en" is still supported at runtime.
  • Tests are unaffected: All test fixtures create their own self-contained apps and are not impacted by this change.

Confidence Score: 4/5

  • Safe to merge — changes are example-only refactoring with no logic regressions; two minor style concerns worth addressing.
  • The functional change is correct and well-scoped to the examples directory. Tests are unaffected. The two flagged issues (unsanitized locale in file-path construction and bare module-level open) are style concerns that could be confusing for developers copying the example pattern into production code, but they do not cause runtime failures in the examples themselves.
  • The four app.py files all share the same two patterns worth fixing before this code is promoted as a copy-paste reference for users.

Important Files Changed

Filename Overview
examples/fastapi-eager/app.py Refactored to read config from gt.config.json and load translations from _gt/.json. Two style concerns: unsanitized locale in file path construction and unguarded module-level open() with no error handling.
examples/fastapi-lazy/app.py Same refactoring pattern as fastapi-eager; shares the same unsanitized locale path and bare module-level open() concerns.
examples/flask-eager/app.py Same refactoring pattern; same unsanitized locale path and bare module-level open() concerns as the FastAPI examples.
examples/flask-lazy/app.py Same refactoring pattern; same unsanitized locale path and bare module-level open() concerns as the other examples.
examples/fastapi-eager/gt.config.json New config file externalising defaultLocale and locales. Note that "en" is intentionally omitted from locales (it's the default/source locale); I18nManager adds it internally.
examples/fastapi-eager/_gt/es.json New hash-keyed translation file for Spanish; content matches prior inline dictionary exactly.
examples/fastapi-eager/_gt/fr.json New hash-keyed translation file for French; content matches prior inline dictionary exactly.

Sequence Diagram

sequenceDiagram
    participant App as app.py (module load)
    participant CFG as gt.config.json
    participant GT as initialize_gt()
    participant MGR as I18nManager
    participant LDR as load_translations(locale)
    participant FS as _gt/<locale>.json

    App->>CFG: open & json.load()
    CFG-->>App: {defaultLocale, locales}
    App->>GT: initialize_gt(default_locale, locales, load_translations, eager_loading)
    GT->>MGR: I18nManager(default_locale, locales=[en,es,fr])
    Note over MGR: Internally adds default_locale to locales set

    alt eager_loading=True
        MGR->>LDR: load_translations("en")
        LDR->>FS: open _gt/en.json (not found)
        LDR-->>MGR: {}
        MGR->>LDR: load_translations("es")
        LDR->>FS: open _gt/es.json
        FS-->>LDR: {hash: "Hola, mundo!", ...}
        LDR-->>MGR: translations dict
        MGR->>LDR: load_translations("fr")
        LDR->>FS: open _gt/fr.json
        FS-->>LDR: {hash: "Bonjour, le monde!", ...}
        LDR-->>MGR: translations dict
    end

    Note over App,MGR: App ready — translations cached in manager
Loading

Last reviewed commit: 09d2f1f

Greptile also left 2 inline comments on this PR.

Refactor all examples to:
- Read configuration from gt.config.json
- Store translations in _gt/<locale>.json files
- Use a load_translations function that reads from _gt/ directory

The custom load_translations function is still required for now but
will be removed in a future release.
print(f"[eager] Loading translations for '{locale}'")
return TRANSLATIONS.get(locale, {})
"""Load translations from _gt/<locale>.json."""
path = GT_DIR / f"{locale}.json"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsanitized locale in file path

The locale string is taken directly from the Accept-Language header (after determine_locale resolves it) and used to construct a file path without any validation. A crafted header containing traversal sequences such as ../../secrets would build a path like _gt/../../secrets.json, potentially reaching files outside the _gt/ directory.

While the appended .json extension and the path.exists() guard reduce practical risk, a defence-in-depth approach would validate that the resolved locale only contains safe characters before using it in path construction:

def load_translations(locale: str) -> dict[str, str]:
    """Load translations from _gt/<locale>.json."""
    import re
    if not re.fullmatch(r"[a-zA-Z0-9_-]+", locale):
        return {}
    path = GT_DIR / f"{locale}.json"
    if path.exists():
        with open(path) as f:
            return json.load(f)
    return {}

The same pattern appears in all four examples:

  • examples/fastapi-lazy/app.py:25
  • examples/flask-eager/app.py:25
  • examples/flask-lazy/app.py:25
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/fastapi-eager/app.py
Line: 25

Comment:
**Unsanitized locale in file path**

The `locale` string is taken directly from the `Accept-Language` header (after `determine_locale` resolves it) and used to construct a file path without any validation. A crafted header containing traversal sequences such as `../../secrets` would build a path like `_gt/../../secrets.json`, potentially reaching files outside the `_gt/` directory.

While the appended `.json` extension and the `path.exists()` guard reduce practical risk, a defence-in-depth approach would validate that the resolved locale only contains safe characters before using it in path construction:

```python
def load_translations(locale: str) -> dict[str, str]:
    """Load translations from _gt/<locale>.json."""
    import re
    if not re.fullmatch(r"[a-zA-Z0-9_-]+", locale):
        return {}
    path = GT_DIR / f"{locale}.json"
    if path.exists():
        with open(path) as f:
            return json.load(f)
    return {}
```

The same pattern appears in all four examples:
- `examples/fastapi-lazy/app.py:25`
- `examples/flask-eager/app.py:25`
- `examples/flask-lazy/app.py:25`

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +19 to +20
with open(BASE_DIR / "gt.config.json") as f:
config = json.load(f)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bare open() at module level with no error handling

gt.config.json is opened at module level without any error handling. If the file is missing (e.g., the app is started from a different working directory, or the file is accidentally excluded from a deployment), Python will raise an unhandled FileNotFoundError that immediately aborts the import with an opaque traceback — no indication of which file is missing or how to fix it.

Notably, initialize_gt already accepts a config_path parameter and delegates to load_gt_config(), which includes proper error handling (returns an empty config when the file is absent, raises a descriptive ValueError for malformed JSON). Since the apps manually pass the resolved values anyway, the manual load is redundant; using config_path directly would be cleaner:

initialize_gt(
    app,
    config_path=str(BASE_DIR / "gt.config.json"),
    load_translations=load_translations,
    eager_loading=True,
)

If the manual load is intentional (e.g., to access the config values in other parts of the module), at minimum wrap it in a try/except with a descriptive message:

try:
    with open(BASE_DIR / "gt.config.json") as f:
        config = json.load(f)
except FileNotFoundError as exc:
    raise FileNotFoundError(
        f"gt.config.json not found at {BASE_DIR / 'gt.config.json'}. "
        "Please create it or run the app from the correct directory."
    ) from exc

The same pattern appears in all four examples:

  • examples/fastapi-lazy/app.py:19-20
  • examples/flask-eager/app.py:19-20
  • examples/flask-lazy/app.py:20-21
Prompt To Fix With AI
This is a comment left during a code review.
Path: examples/fastapi-eager/app.py
Line: 19-20

Comment:
**Bare `open()` at module level with no error handling**

`gt.config.json` is opened at module level without any error handling. If the file is missing (e.g., the app is started from a different working directory, or the file is accidentally excluded from a deployment), Python will raise an unhandled `FileNotFoundError` that immediately aborts the import with an opaque traceback — no indication of which file is missing or how to fix it.

Notably, `initialize_gt` already accepts a `config_path` parameter and delegates to `load_gt_config()`, which includes proper error handling (returns an empty config when the file is absent, raises a descriptive `ValueError` for malformed JSON). Since the apps manually pass the resolved values anyway, the manual load is redundant; using `config_path` directly would be cleaner:

```python
initialize_gt(
    app,
    config_path=str(BASE_DIR / "gt.config.json"),
    load_translations=load_translations,
    eager_loading=True,
)
```

If the manual load is intentional (e.g., to access the config values in other parts of the module), at minimum wrap it in a `try/except` with a descriptive message:

```python
try:
    with open(BASE_DIR / "gt.config.json") as f:
        config = json.load(f)
except FileNotFoundError as exc:
    raise FileNotFoundError(
        f"gt.config.json not found at {BASE_DIR / 'gt.config.json'}. "
        "Please create it or run the app from the correct directory."
    ) from exc
```

The same pattern appears in all four examples:
- `examples/fastapi-lazy/app.py:19-20`
- `examples/flask-eager/app.py:19-20`
- `examples/flask-lazy/app.py:20-21`

How can I resolve this? If you propose a fix, please make it concise.

@moss-bryophyta moss-bryophyta changed the title examples: use gt.config.json and _gt/<locale>.json for translations chore: use gt.config.json and _gt/<locale>.json for example translations Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant